package vip.liux.contracts.security;

import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.core.log.LogMessage;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.InternalAuthenticationServiceException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.authentication.event.InteractiveAuthenticationSuccessEvent;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.context.SecurityContextHolderStrategy;
import org.springframework.security.web.authentication.*;
import org.springframework.security.web.context.RequestAttributeSecurityContextRepository;
import org.springframework.security.web.context.SecurityContextRepository;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import java.io.IOException;

@Component
public class SignManager implements AuthenticationManagerAware {
    protected final Log logger = LogFactory.getLog(getClass());

    private SecurityContextHolderStrategy securityContextHolderStrategy = SecurityContextHolder
            .getContextHolderStrategy();

    private AuthenticationSuccessHandler successHandler = new ForwardAuthenticationSuccessHandler("/api/account/authentication/token");

    // private AuthenticationFailureHandler failureHandler = new SimpleUrlAuthenticationFailureHandler();
    private AuthenticationFailureHandler failureHandler = (request, response, exception) -> {
        throw exception;
    };

    private SecurityContextRepository securityContextRepository = new RequestAttributeSecurityContextRepository();

    private RememberMeServices rememberMeServices = new NullRememberMeServices();

    private AuthenticationManager authenticationManager;

    protected ApplicationEventPublisher eventPublisher;


    public void authentication(String username, String password)
            throws IOException, ServletException {
        authentication(new UsernamePasswordAuthenticationToken(username, password));
    }
    /**
     * 执行认证。
     *
     * @throws IOException      如果发生输入或输出错误
     * @throws ServletException 如果发生Servlet异常
     */
    public void authentication(Authentication authRequest)
            throws IOException, ServletException {
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        if (attributes == null) {
            logger.warn("当前线程中未找到ServletRequestAttributes。您是否在使用RequestContextFilter?");
            return;
        }
        HttpServletRequest request = attributes.getRequest();
        HttpServletResponse response = attributes.getResponse();
        try {

            Authentication authenticationResult = getAuthenticationManager().authenticate(authRequest);
            if (authenticationResult == null) {
                return;
            }
            successfulAuthentication(request, response, authenticationResult);
        } catch (InternalAuthenticationServiceException failed) {
            this.logger.error("尝试认证用户时发生内部错误。", failed);
            unsuccessfulAuthentication(request, response, failed);
        } catch (AuthenticationException ex) {
            // 认证失败
            unsuccessfulAuthentication(request, response, ex);
        }
    }

    /**
     * 当用户成功认证时调用。
     *
     * @param request    导致成功认证的请求
     * @param response   响应
     * @param authResult 在认证过程中创建的<tt>Authentication</tt>对象
     * @throws IOException      如果发生输入或输出错误
     * @throws ServletException 如果发生Servlet异常
     */
    protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response,
                                            Authentication authResult) throws IOException, ServletException {
        SecurityContext context = this.securityContextHolderStrategy.createEmptyContext();
        context.setAuthentication(authResult);
        this.securityContextHolderStrategy.setContext(context);
        this.securityContextRepository.saveContext(context, request, response);
        if (this.logger.isDebugEnabled()) {
            this.logger.debug(LogMessage.format("将SecurityContextHolder设置为%s", authResult));
        }
        this.rememberMeServices.loginSuccess(request, response, authResult);
        if (this.eventPublisher != null) {
            this.eventPublisher.publishEvent(new InteractiveAuthenticationSuccessEvent(authResult, this.getClass()));
        }
        this.successHandler.onAuthenticationSuccess(request, response, authResult);
    }

    /**
     * 当认证尝试失败时调用。
     *
     * @param request  导致认证失败的请求
     * @param response 响应
     * @param failed   拒绝认证时抛出的异常
     * @throws IOException      如果发生输入或输出错误
     * @throws ServletException 如果发生Servlet异常
     */
    protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response,
                                              AuthenticationException failed) throws IOException, ServletException {
        this.securityContextHolderStrategy.clearContext();
        this.logger.trace("处理认证请求失败", failed);
        this.logger.trace("清除SecurityContextHolder");
        this.logger.trace("处理认证失败");
        this.rememberMeServices.loginFail(request, response);
        this.failureHandler.onAuthenticationFailure(request, response, failed);
    }

    private AuthenticationManager getAuthenticationManager() {
        return authenticationManager;
    }


    @Autowired(required = false)
    public void setAuthenticationManager(AuthenticationManager authenticationManager) {
        this.authenticationManager = authenticationManager;
    }

    @Autowired(required = false)
    public void setSecurityContextHolderStrategy(SecurityContextHolderStrategy securityContextHolderStrategy) {
        this.securityContextHolderStrategy = securityContextHolderStrategy;
    }

    @Autowired(required = false)
    public void setSecurityContextRepository(SecurityContextRepository securityContextRepository) {
        this.securityContextRepository = securityContextRepository;
    }

    @Autowired(required = false)
    public void setRememberMeServices(RememberMeServices rememberMeServices) {
        this.rememberMeServices = rememberMeServices;
    }

    @Autowired(required = false)
    public void setEventPublisher(ApplicationEventPublisher eventPublisher) {
        this.eventPublisher = eventPublisher;
    }

    @Autowired(required = false)
    public void setSuccessHandler(AuthenticationSuccessHandler successHandler) {
        this.successHandler = successHandler;
    }

    @Autowired(required = false)
    public void setFailureHandler(AuthenticationFailureHandler failureHandler) {
        this.failureHandler = failureHandler;
    }
}